/*
 * Copyright (C) 2012  Romain DUBOIS
 * 
 * This file is part of NeMoS - Network and Modular Software
 * 
 * NeMoS is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * NeMoS is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with NeMoS.  If not, see <http://www.gnu.org/licenses/>.
 */
package nemos.swing.swixml.library;

import java.util.HashMap;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;

import nemos.swing.swixml.SwixmlMessage;
import nemos.util.log.NemosLogger;

import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceReference;
import org.osgi.util.tracker.ServiceTracker;
import org.swixml.Converter;
import org.swixml.ConverterLibrary;
import org.swixml.Factory;
import org.swixml.LayoutConverter;
import org.swixml.LayoutConverterLibrary;
import org.swixml.TagLibrary;

/**
 * AbstractLibraryManager : The abstract library manager class.
 * 
 * <p>
 * This class is used to convert OSGi services into SwiXML customization classes
 * ({@link Converter}s, {@link LayoutConverter}s and {@link Factory Factories}).
 * </p>
 * 
 * <p>
 * It tracks the OSGi services and register the adapted service into the
 * appropriate library ({@link ConverterLibrary}, {@link LayoutConverterLibrary}
 * or {@link TagLibrary}).
 * </p>
 * 
 * 
 * @param <T>
 *            The tracked services type
 * @param <S>
 *            The SwiXML customization type
 * @param <K>
 *            The customization key type
 * 
 * @author Romain DUBOIS
 */
@Component(componentAbstract = true)
public abstract class AbstractLibraryManager<T, S, K> {

    /** The existing OSGi converters */
    private final Map<K, SortedSet<ServiceReference<T>>> mOsgiConverters;
    /** The SwiXML converters */
    private final Map<K, S> mOriginalConverters;
    /** The OSGi service tracker */
    private ServiceTracker<T, T> mTracker;
    /** The logger */
    private NemosLogger mLogger;

    /**
     * Constructor
     */
    public AbstractLibraryManager() {
        super();

        mOsgiConverters = new HashMap<K, SortedSet<ServiceReference<T>>>();
        mOriginalConverters = new HashMap<K, S>();
    }

    /**
     * Start the component.
     * 
     * @param pContext
     *            The bundle context
     */
    @Activate
    public void start(final BundleContext pContext) {
        mLogger = new NemosLogger(AbstractLibraryManager.class);
        mTracker = new ServiceTracker<T, T>(pContext, getConverterType(), null) {
            @Override
            public T addingService(final ServiceReference<T> pReference) {
                final T lService = super.addingService(pReference);
                bindConverter(pReference, lService);
                return lService;
            }

            @Override
            public void removedService(final ServiceReference<T> pReference, final T pService) {
                unbindConverter(pReference, pService);
                super.removedService(pReference, pService);
            }
        };
        mTracker.open();
    }

    /**
     * Stop the component.
     */
    public void stop() {
        mTracker.close();
    }

    /**
     * Get the converter type.
     * 
     * @return The tracked services type
     */
    protected abstract String getConverterType();

    /**
     * Wrap an OSGi service into a SwiXML customization class.
     * 
     * @param pType
     *            The type handled by the converter
     * @param pOsgiConverter
     *            The converter to wrap
     * @return The wrapped converter
     */
    protected abstract S wrap(K pType, T pOsgiConverter);

    /**
     * Register a converter into the library.
     * 
     * @param pType
     *            The type handled by the converter
     * @param pSwixmlConverter
     *            The customization converter
     */
    protected abstract void register(K pType, S pSwixmlConverter);

    /**
     * Get type handled by the converter.
     * 
     * @param pReference
     *            The converter service reference
     * @param pConverter
     *            The converter service
     * @return The converter key
     */
    protected abstract K getConverterKey(ServiceReference<T> pReference, T pConverter);

    /**
     * Get the current service for the provided type.
     * 
     * @param pType
     *            The type
     * @return The matching service
     */
    protected abstract S getCurrentService(K pType);

    /**
     * Update the SwiXML library.
     * 
     * @param pType
     *            The converter type
     * @param pFirst
     *            The first converter reference
     * @param pPrevious
     *            The previous converter reference
     * @param pNewlyAdded
     *            The newly added converter
     */
    private void updateLibrary(final K pType, final ServiceReference<T> pFirst,
            final ServiceReference<T> pPrevious, final T pNewlyAdded) {

        // Update only if a change occurred
        if ((pFirst == null && pPrevious != null) || (pFirst != null && !pFirst.equals(pPrevious))) {

            // Use original if no OSGi service
            if (pFirst == null) {
                register(pType, mOriginalConverters.get(pType));
            } else {
                T lConverter = pNewlyAdded;
                if (lConverter == null) {
                    lConverter = mTracker.getService(pFirst);
                }
                if (lConverter == null) {
                    mLogger.error(SwixmlMessage.LIBRARY_UPDATE_NO_SERVICE,
                            pFirst.getProperty(Constants.SERVICE_PID), getConverterType(), pType);
                } else {
                    register(pType, wrap(pType, lConverter));
                }
            }
        }
    }

    /**
     * Bind an OSGi converter.
     * 
     * @param pReference
     *            The service reference
     * @param pService
     *            The service
     */
    public void bindConverter(final ServiceReference<T> pReference, final T pService) {
        if (pService == null) {
            return;
        }
        final K lType = getConverterKey(pReference, pService);

        synchronized (mOsgiConverters) {
            // Ensure converters sorted set exists
            SortedSet<ServiceReference<T>> lConverters = mOsgiConverters.get(lType);
            if (lConverters == null) {
                lConverters = new TreeSet<ServiceReference<T>>();
                mOsgiConverters.put(lType, lConverters);
            }

            // Keep the original converter
            ServiceReference<T> lPrevious = null;
            if (lConverters.isEmpty()) {
                mOriginalConverters.put(lType, getCurrentService(lType));
            } else {
                lPrevious = lConverters.first();
            }

            // Add the new converter and update the library
            lConverters.add(pReference);
            if (pReference == lConverters.first()) {
                updateLibrary(lType, lConverters.first(), lPrevious, pService);
            } else {
                updateLibrary(lType, lConverters.first(), lPrevious, null);
            }
        }
    }

    /**
     * Unbind an OSGi converter.
     * 
     * @param pReference
     *            The service reference
     * @param pConverter
     *            The converter
     */
    public void unbindConverter(final ServiceReference<T> pReference, final T pConverter) {
        final K lType = getConverterKey(pReference, pConverter);

        synchronized (mOsgiConverters) {
            // If it is the first, it is registered in library
            final SortedSet<ServiceReference<T>> lConverters = mOsgiConverters.get(lType);

            ServiceReference<T> lPrevious = null;
            ServiceReference<T> lNext = null;
            if (lConverters != null && !lConverters.isEmpty()) {
                // Collect previous first
                lPrevious = lConverters.first();

                // Remove the reference
                lConverters.remove(pReference);

                if (!lConverters.isEmpty()) {
                    lNext = lConverters.first();
                }
            }

            updateLibrary(lType, lNext, lPrevious, null);
        }
    }
}
